package conput;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class ReadWriteLock {

    int activeReaders;
    int activeWriters;
    int pendingReaders;
    int pendingWriters;
    final ReentrantLock lock = new ReentrantLock();
    final Condition canRead = lock.newCondition();
    final Condition canWrite = lock.newCondition();
    Thread writer;

    void readLock() throws InterruptedException {
        lock.lock();
        try {
            while (activeWriters != 0 && pendingWriters != 0) {
                ++pendingReaders;
                try {
                    canRead.await();
                } finally {
                    --pendingReaders;
                }
            }
            assert activeWriters == 0 && pendingWriters == 0;
            ++activeReaders;
        } finally {
            lock.unlock();
        }
    }

    void writeLock() throws InterruptedException {
        lock.lock();
        try {
            while (activeWriters != 0 || activeReaders != 0) {
                ++pendingWriters;
                try {
                    canWrite.await();
                } finally {
                    --pendingWriters;
                }
            }
            assert activeWriters == 0 && activeReaders == 0;
            activeWriters = 1;
            writer = Thread.currentThread();
        } finally {
            lock.unlock();
        }
    }

    void unlockWrite() {
        lock.lock();
        try {
            if (writer != Thread.currentThread() || activeWriters == 0)
                throw new IllegalMonitorStateException();
            activeWriters = 0;
            if (pendingWriters != 0)
                canWrite.signal();
            else canRead.signalAll();
        } finally {
            lock.unlock();
        }
    }

    void unlockRead() {
        lock.lock();
        try {
            if (activeReaders == 0)
                throw new IllegalMonitorStateException();
            if (--activeReaders == 0 && pendingWriters != 0)
                canWrite.signal();
        } finally {
            lock.unlock();
        }
    }
}
